/*
 *
 * Written by NIIBE Yutaka
 *
 * Enhanced by Wiktor Langowski <jolkaczad@gmail.com>
 *  - controlling display modes via USB
 *  - saving settings and bitmap/text data to EEPROM
 *  - saving bitmap/text data directly to RAM
 *  - rearranging the functions into a library-like form
 *  - variable width characters support
 *
 * Copyright (C) 2009 Free Software Initiative of Japan
 *
 * Distributed under the terms of GPLv3 (or later)
 */

/*
 * You need libusb to use this program.
 *
 * On Debian, type:
 * $ apt-get libusb-dev
 */

/*
 * When compiling the functions alone,
 * $ gcc -Wall -c -Wno-unused-function akiusb-host.c
 * can be used.
 *
 * While including the funtions to your own file, you must remember
 * to link libusb by appending "-lusb" as a gcc parameter.
 */
#include "akiusb-host.h"
#include "etl-font-r-host.c.inc"

int
set_settings (usb_dev_handle *udev, unsigned char *eep_settings, int destination)
{
  int r;

  if (destination == EEPROM_SAVE)
    {
      if ((r = send_cmd_set_settings_eeprom (udev, eep_settings)) < 0)
	return r;
      if ((r = send_cmd_reset (udev)) < 0)
	return r;
    }
  else if (destination == EEPROM_NSAVE)
    {
      if ((r = send_cmd_set_settings_ram (udev, eep_settings)) < 0)
	return r;
    }
  else
    {
      perror ("SET_SETTINGS no such destination\n");
      return -1;
    }

  return 0;
}

int
get_settings (usb_dev_handle *udev, unsigned char *settings, int destination)
{
  int r;

  if (destination == EEPROM_SAVE)
    {
      if ((r = send_cmd_get_settings_eeprom (udev, settings)) < 0)
	return r;
    }
  else if (destination == EEPROM_NSAVE)
    {
      if ((r = send_cmd_get_settings_ram (udev, settings)) < 0)
	return r;
    }
  else
    {
      perror ("GET_SETTINGS no such destination\n");
      return -1;
    }

  return 0;
}

int
compare_usb_string (usb_dev_handle *udev, int index, char *str)
{
  int len;
  char buf[256];

  if ((len = usb_get_string_simple (udev, index, buf, 256)) < 0)
    {
      perror ("USB_GET_STRING_SIMPLE failed\n");
      return -1;
    }
  
  return strncmp (buf, str, len);
}

int
send_cmd_setcol (usb_dev_handle *udev, unsigned char col)
{
  int r;

  usbbuf[0] = AKIUSB_SETCOL;
  usbbuf[1] = col;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_clrscr (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_CLRSCR;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_vmode (usb_dev_handle *udev, unsigned char mode, unsigned char value)
{
  int r;

  usbbuf[0] = AKIUSB_VMODE;
  usbbuf[1] = mode;
  if (mode == 0)
    usbbuf[2] = value;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, (mode == 0) ? 3 : 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_char (usb_dev_handle *udev, unsigned char c)
{
  int r;

  usbbuf[0] = AKIUSB_CHAR;
  usbbuf[1] = c;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_setdsp (usb_dev_handle *udev, unsigned char col)
{
  int r;

  usbbuf[0] = AKIUSB_SETDSP;
  usbbuf[1] = col;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_weeprom (usb_dev_handle *udev, const char *text, int len)
{
  size_t size = len;
  int i;
  int r;
  int a;

  for (i = 0; i < size;)
    {
      a = (size >= 7) ? 7 : size;

      usbbuf[0] = AKIUSB_WEEPROM;
      memcpy (&usbbuf[1], &text[i], a);
      
      if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, a+1, 0)) < 0)
	{
	  perror ("USB_INTERRUPT_WRITE 0");
	  return r;
	}
      i += a;
    }

  return 0;
}

int
send_cmd_ceeprom (usb_dev_handle *udev, unsigned char a)
{
  int r;

  usbbuf[0] = AKIUSB_CEEPROM;
  usbbuf[1] = a;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 2, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_reset (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_RESET;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_set_onetime (usb_dev_handle *udev, const char *text, int len)
{
  size_t size = len;
  int i;
  int r;
  int a;

  for(i = 0; i < size;)
    {
      a = (size >= 7) ? 7 : size;

      usbbuf[0] = AKIUSB_SET_ONETIME;
      memcpy (&usbbuf[1], &text[i], a);

      if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, a+1, 0)) < 0)
	{
	  perror ("USB_INTERRUPT_WRITE 0");
	  return r;
	}
      i += a;
    }
  /* to show that text transfer has ended so finishing tasks
   * can be performed on device */
  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_set_settings_ram (usb_dev_handle *udev, unsigned char *settings)
{
  int r;

  usbbuf[0] = AKIUSB_SETTINGS_RAM;
  memcpy (&usbbuf[1], settings, 7);

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 8, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_stop (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_STOP;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_go (usb_dev_handle *udev)
{
  int r;

  usbbuf[0] = AKIUSB_GO;

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 1, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_cmd_get_settings_ram (usb_dev_handle *udev, unsigned char *settings)
{
  /*
   * I started thinking if this has a use in real world,
   * probably not.
   */
  return -1;
}

int
send_cmd_get_settings_eeprom (usb_dev_handle *udev, unsigned char *settings)
{
  int r;

  if ((r = usb_control_msg (udev, REQUESTTYPE, AKIUSB_GET_EEPROM_SETT, 0, 0, \
		       (char *) settings, EEPROM_SASIZE, 5000)) < 0)
    {
      perror ("USB_CONTROL err");
      return r;
    }
  return 0;
}

int
send_cmd_set_settings_eeprom (usb_dev_handle *udev, unsigned char *settings)
{
  int r;

  usbbuf[0] = AKIUSB_SETTINGS_EEPROM;
  memcpy (&usbbuf[1], settings, 7);

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 8, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
send_one_column (usb_dev_handle *udev, unsigned char *bits)
{
  int r;

  usbbuf[0] = AKIUSB_DRAW;
  if (bits != NULL)
    {
      usbbuf[1] = bits[0];
      usbbuf[2] = bits[1];
    }
  else
    {
      usbbuf[1] = 0;
      usbbuf[2] = 1;
    }

  if ((r = usb_interrupt_write (udev, ENDPOINT, usbbuf, 3, 0)) < 0)
    {
      perror ("USB_INTERRUPT_WRITE 0");
      return r;
    }

  return 0;
}

int
render_text_scrolling (usb_dev_handle *udev, char *text)
{
  struct context context = { 0, 0, NULL };
  int col = 0;

  context.text = text;

  while (1)
    {
      struct pollfd pfd = { 0 /* standard input */, POLLIN, 0 };
      int r;
      unsigned char * bits;

      /* Any input stops this routine */
      if ((r = poll (&pfd, 1, TIMEOUT)) < 0)
	{
	  perror ("poll\n");
	  exit (1);
	}

      if (r)
	break;

      bits = get_bits_for_one_column (&context);
      if ((r = send_one_column (udev, bits)) < 0)
	return r;

      if ((r = send_cmd_setdsp (udev, col)) < 0)
	return r;

      if (++col >= 64)
	col = 0;
    }

  /* dummy read */
  {
    char buf[BUFSIZE];

    read (0, buf, BUFSIZE);
  }

  return 0;
}

int
manage_usb (struct usb_bus **bus, struct usb_device **dev, usb_dev_handle **udev)
{
  int i;
  
  usb_init();

  usb_find_busses();
  usb_find_devices();

  for (*bus = usb_busses; *bus; *bus = (*bus)->next)
    for (*dev = (*bus)->devices; *dev; *dev = (*dev)->next)
      {
	printf ("%04x:%04x\n", (*dev)->descriptor.idVendor, (*dev)->descriptor.idProduct);

	/* Check the vendor ID and product ID */
	if ((*dev)->descriptor.idVendor == USB_VENDOR
	    && (*dev)->descriptor.idProduct == USB_PRODUCT)
	  {
	    /* XXX: assert dev->descriptor.bNumConfigurations == 1 */

	    /* Check the interface */
	    for (i = 0; i < (*dev)->config[0].bNumInterfaces; i++)
	      {
	  	/* XXX
		 * assert dev->config[0].interface[i].num_altsetting == 1
		 */
		
		/* Check the class */
	  	if ((*dev)->config[0].interface[i].altsetting[0].bInterfaceClass
		    == 0xff) /* Vendor specific */
		  {
		    /* Ok, it looks like our product */
		    /* Check the strings */

		    *udev = usb_open (*dev);
		    if (!udev)
		      {
			perror ("USB_OPEN failed\n");
			exit (1);
		      }

		    if (compare_usb_string (*udev, (*dev)->descriptor.iManufacturer,
					    USB_MANUFACTURER_STR) == 0
			&& compare_usb_string (*udev, (*dev)->descriptor.iProduct,
					       USB_PRODUCT_STR) == 0
			&& compare_usb_string (*udev, (*dev)->descriptor.iSerialNumber,
					       USB_SERIALNUMBER_STR) == 0)
		      goto found;

		    usb_close (*udev);
		  }
	      }
	  }
      }

  puts ("No device found");
  exit (1);


 found:

  printf ("interface %d\n", i);
  if (usb_set_configuration (*udev, 1) < 0)
    {
      perror ("USB_SET_CONFIGURATION");
      /* Fall through */
    }

  if (usb_claim_interface (*udev, i) < 0)
    {
      perror ("USB_CLAIM_INTERFACE");
      return 1;
    }

  return 0;
}

int char_width (unsigned char c)
{
  if (c < MIN_CHAR || c > MAX_CHAR)
    return -1;
  else
    return etl_font_data[(c - MIN_CHAR)*SEGM_SIZE];
}

unsigned char *char_bits (unsigned char c)
{
  if (c < MIN_CHAR || c > MAX_CHAR)
    return NULL;
  else
    return &etl_font_data[(c - MIN_CHAR)*SEGM_SIZE*2+1];
}

unsigned char *
get_bits_for_one_column (struct context *ctx)
{
  int col, index;
  unsigned char c;
  unsigned char *bits;
  int len;

  col = ctx->char_col++;
  if (ctx->char_col >= char_width (ctx->text[ctx->text_index]))
    {
      ctx->char_col = 0;
      index = ctx->text_index++;
      if (ctx->text[ctx->text_index] == '\0')
	ctx->text_index = 0;
    }
  else
    index = ctx->text_index;

  c = ctx->text[ctx->text_index];
  len = char_width (c);
  bits = char_bits (c);

  if (bits != NULL)
    return &bits[col*2];
  else
    return NULL;
}
